/**
 * 
 */
package jvs.core.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.StringTokenizer;

import jvs.vfs.io.ChunkedOutputStream;
import x.java.io.File;

/**
 * @author qiangli
 * 
 */
public class Response extends Message {
	// Status Codes
	public final static String CODE200 = "200 OK";

	public final static String CODE201 = "201 Created";

	public final static String CODE202 = "202 Accepted";

	public final static String CODE204 = "204 No Content";

	public final static String CODE301 = "301 Moved Permanently";

	public final static String CODE302 = "302 Moved Temporarily";

	public final static String CODE304 = "304 Not Modified";

	public final static String CODE400 = "400 Bad Request";

	public final static String CODE401 = "401 Unauthorized";

	public final static String CODE403 = "403 Forbidden";

	public final static String CODE404 = "404 Not Found";

	public final static String CODE500 = "500 Internal Server Error";

	public final static String CODE501 = "501 Not Implemented";

	public final static String CODE502 = "502 Bad Gateway";

	public final static String CODE503 = "503 Service Unavailable";

	//
	public final static String SERVER = "SLP 1.0";

	public static final String TRANSFER_ENCODING_CHUNKED = "chunked";

	//
	public String statusCode = CODE200;

	public String transferEncoding;

	public String location;

	//
	private OutputStream os;

	public Response() {
	}

	public void setOutputStream(OutputStream os) {
		this.os = os;
	}

	public void send(String c) {
		sendResponse(c);
	}
	
	private void sendResponse(String c) {
		PrintStream w = new PrintStream(os);
		if (c != null) {
			contentLength = c.length();
			sendHeaders(w);
			w.append(c);
		} else {
			sendHeaders(w);
		}
		w.flush();
	}

	public void redirect(URI uri) throws Exception {
		log("redirect: " + uri);
		//
		StringBuilder sb = new StringBuilder();
		sb.append("<html><head><title>URI Redirect</title></head><body>");
		sb.append("<p>Use peer uri: <a href=\"" + uri + "\">" + uri + "</a>");
		sb.append("</p></body></html>");
		//
		this.statusCode = CODE301;
		this.contentType = TYPE_TEXT_HTML;
		this.location = uri.toString();

		sendResponse(sb.toString());
	}

	public void error(Exception e) {
		StringWriter sw = new StringWriter();
		PrintWriter sb = new PrintWriter(sw);
		sb.append("<html><head><title>Exception</title></head><body>");
		sb.append("<p>Oops!</p><div>");
		e.printStackTrace(sb);
		sb.append("</div></body></html>");
		//
		this.statusCode = CODE500;
		this.contentType = TYPE_TEXT_HTML;
		sendResponse(sw.toString());
	}

	public void ni(String s) {
		String c = "<html><head><title></title></head><body>"
				+ "<h1>Not implemented " + s + "</h1></body></html>";
		//
		this.statusCode = CODE501;
		this.contentType = TYPE_TEXT_HTML;
		sendResponse(c);
	}

	public void notModified(long date) {
		this.statusCode = Response.CODE304;
		this.date = date;
		sendResponse(null);
	}

	private void sendHeaders(PrintStream w) {
		w.append("HTTP/1.1 " + statusCode + "\r\n");
		w.append("Content-Type: " + contentType + "\r\n");
		if (contentLength >= 0) {
			w.append("Content-Length: " + contentLength + "\r\n");
		}
		if (location != null) {
			w.append("Location: " + location + "\r\n");
		}
		if (transferEncoding != null) {
			w.append("Transfer-Encoding: " + transferEncoding + "\r\n");
		}
		if (lastModified > 0) {
			w.append("Last-Modified: " + formatRFC1123(lastModified) + "\r\n");
		}
		if (date > 0) {
			w.append("Date: " + formatRFC1123(date) + "\r\n");
		}
		w.append("Server: " + SERVER + "\r\n");
		w.append("Connection: close\r\n\r\n");
		w.flush();
	}
	
	public void send(InputStream is, boolean close) throws Exception {
		PrintStream w = new PrintStream(os);
		sendHeaders(w);
		OutputStream cos = (transferEncoding != null) ? new ChunkedOutputStream(os, false) : os;
		try {
			copy(is, cos); 
		} catch (Exception e) {
			throw e;
		} finally {
			if (close) {
				try {
					is.close();
				} catch (Exception e) {
					//
				}
			}
			cos.close();
		}
	}
	
	protected void copy(InputStream is, OutputStream os) throws IOException {
		byte[] b = new byte[File.BUFSIZE];
		int cnt = -1;
		//int total = 0;
		while ((cnt = is.read(b)) != -1) {
			if (cnt == 0) {
				continue;
			}
			//total += cnt;
			//System.out.println("total: " + total + " cnt: " + cnt);
			os.write(b, 0, cnt);
			os.flush();
		}
	}
	
	/*
	private int copy(InputStream in, OutputStream out) throws IOException {
		byte[] buf = new byte[File.BUFSIZE];
		int cnt = 0;
		String thn = Thread.currentThread().toString();
		while (true) {
			int read = in.read(buf);
			if (read > 0) {
				cnt += read;
				out.write(buf, 0, read);
				out.flush();
				
				System.out.println(thn+ " count: " + cnt);
			} else {
				break;
			}
		}
		return cnt;
	}
*/
	public void parse(InputStream is) throws IOException {
		startLine = readLine(is);
		
		parseStartLine();
		//
		headers = new ArrayList<String>();
		String line = null;
		for (int i = 0;;) {
			line = readLine(is);
			if (line == null || line.length() == 0) {
				break;
			} else if (line.charAt(0) == '\t' || line.charAt(0) == ' ') {
				headers.add(i - 1, headers.get(i - 1) + line); // multiple-line
				continue;
			}
			headers.add(i++, line);
			//
			if (line.startsWith("Content-Type:")) {
				contentType = line.substring(13).trim();
			} else if (line.startsWith("Content-Length:")) {
				try {
					contentLength = Integer.parseInt(line.substring(15)
							.trim());
				} catch (Exception e) {
				}
			} else if (line.startsWith("Content-Encoding:")) {
				contentEncoding = line.substring(17).trim();
			} else if (line.startsWith("Transfer-Encoding:")) {
				transferEncoding = line.substring(18).trim();
			} else if (line.startsWith("Location:")) {
				location = line.substring(9).trim();
			}	
		}
	}
	
	private void parseStartLine() {
		//HTTP/1.1 status
		log("Reponse Line: |" + startLine + "|");

		StringTokenizer st = new StringTokenizer(startLine);
		String[] r = new String[2];
		for (int i = 0; i < r.length; i++) {
			r[i] = st.nextToken();
		}
		statusCode = r[1].trim();
	}
}
